#pragma once
// #define timers
#include <algorithm>
#include <any>
#include <cmath>
#include <functional>
#include <iostream>
#include <map>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#include "constants.h"

using namespace std;

#ifdef timers
clock_t start = clock();
#endif

void print_error(const string error);
double numeric_input(const string text);

double solve_determinant(const vector<vector<double>>& mat) {
  const int n = mat.size();

  vector<vector<double>> upperTriangular = mat;

  for (int i = 0; i < n; i++) {
    for (int j = i + 1; j < n; j++) {
      const double factor = upperTriangular[j][i] / upperTriangular[i][i];
      for (int k = i; k < n; k++)
        upperTriangular[j][k] -= factor * upperTriangular[i][k];
    }
  }

  double det = 1.0;
  for (int i = 0; i < n; i++) det *= upperTriangular[i][i];

  return det;
}
string get_rand_matrix(const int n, const int limit) {
  random_device rd;
  mt19937 gen(rd());
  uniform_int_distribution<> dis(1, limit);

  string result;
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j)
      result += to_string(dis(gen)) + (j < n - 1 ? "," : ";");
  }

  return result;
}
double convert_degree_to_radians(const double degree) {
  return degree * M_PI / 180;
}
string get_value(const string symbol, map<char, string> alphabet) {
  string result;
  for (const char number : symbol) result += alphabet[number];
  return result;
}
string get_string_from_any(const any value) {
  if (typeid(double) == value.type())
    return to_string(int(any_cast<double>(value)));
  else if (typeid(int) == value.type())
    return to_string(any_cast<int>(value));
  else if (typeid(const char*) == value.type())
    return any_cast<const char*>(value);
  else if (typeid(string) == value.type())
    return any_cast<string>(value);

  return "";
}
string get_subscript(const any symbol) {
  return get_value(get_string_from_any(symbol), SUBSCRIPT_ALPHABET);
}
string get_superscript(const any symbol) {
  return get_value(get_string_from_any(symbol), SUPERSCIRPT_ALPHABET);
}
string get_greek_letter(const string symbol) { return GREEK_ALPHABET[symbol]; }
string get_math_symbol(const string symbol) { return MATH_ALPHABET[symbol]; }
int get_choice(const string header, const vector<string>& choices) {
  cout << BOLD << header << ":" << RESET << endl;
  for (int i = 0; i < choices.size(); i++) {
    cout << GREEN_TEXT << i + 1 << ". " << RESET << YELLOW_TEXT << choices[i]
         << RESET << endl;
  }
  cout << GREEN_TEXT << "0. " << YELLOW_TEXT << "Выход\n" << RESET;

  int choice = numeric_input("");
  if (!choice) {
#ifdef timers
    cout << endl
         << "Общее время исполнения программы: "
         << double(clock() - start) / CLOCKS_PER_SEC << endl;
#endif

    exit(0);
  } else if (choice < 1 || choice > choices.size()) {
    print_error("Введите число от 0 до " + to_string(choices.size()));
    return get_choice(header, choices);
  }

  return choice;
}
void print_equation(const vector<any> equation_parts) {
  short index;
  cout << GREEN_TEXT << BOLD;
  for (const auto& part : equation_parts) {
    index++;
    if (index == equation_parts.size()) cout << " = ";

    cout << get_string_from_any(part);
  }
  cout << RESET;
}
void print_error(const string error) {
  cout << RED_TEXT << BOLD << error << RESET << endl;
}

string input(const string text) {
  cout << BLUE_TEXT << BOLD << text << ": ";
  string input;
  cin >> input;
  cout << RESET;
  return input;
}
double numeric_input(const string text) {
  cin.exceptions(ios::failbit);

  while (true) {
    cout << BLUE_TEXT << BOLD;
    if (!text.empty())
      cout << text << ": ";
    else
      cout << ">> ";

    double input;

    try {
      cin >> input;
      cout << RESET;
      return input;
    } catch (const exception e) {
      print_error("Должно быть введено число");
      cin.clear();  // Clear the error state
      cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }
  }
}

vector<double> solveGauss(vector<vector<double>> A, vector<double> B) {
  int n = A.size();

  for (int i = 0; i < n; i++) {
    double max_element = abs(A[i][i]);
    int max_row = i;
    for (int j = i + 1; j < n; j++) {
      if (abs(A[j][i]) > max_element) {
        max_element = abs(A[j][i]);
        max_row = j;
      }
    }

    if (max_row != i) {
      swap(A[i], A[max_row]);
      swap(B[i], B[max_row]);
    }

    for (int j = i + 1; j < n; j++) {
      double coefficient = -A[j][i] / A[i][i];
      for (int k = i; k < n; k++) {
        if (i == k)
          A[j][k] = 0;
        else
          A[j][k] += coefficient * A[i][k];
      }
      B[j] += coefficient * B[i];
    }
  }

  vector<double> X(n);
  for (int i = n - 1; i >= 0; i--) {
    X[i] = B[i];
    for (int j = i + 1; j < n; j++) X[i] -= A[i][j] * X[j];
    X[i] /= A[i][i];
  }

  return X;
}

pair<vector<vector<double>>, vector<double>> parseEquations(string equations) {
  vector<vector<double>> A;
  vector<double> B;

  stringstream ss(equations);
  string equation;
  while (getline(ss, equation, ';')) {
    vector<string> operands;
    string operand;
    stringstream ss_equation(equation);
    while (getline(ss_equation, operand, '+')) {
      operands.push_back(operand);
    }

    vector<double> row;
    for (const string& operand : operands) {
      string result;
      for (int i = 0; i < operand.find("x"); i++) {
        result += operand[i];
      }
      row.push_back(stod(result));
    }
    A.push_back(row);

    string b_str = equation.substr(equation.find("=") + 1);
    B.push_back(stod(b_str));
  }

  return make_pair(A, B);
}
